package com.btl.component.crypto.aes.util;

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.StopWatch;

import javax.crypto.Cipher;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.Certificate;
import java.util.Base64;

@Slf4j
public class RSACryptoUtil {

    private static final String keystorePath = "cert/server.p12";

    private static final String keystorePassword = "123456";

    private static final String certAlias = "mycert";

    private static volatile PublicKey publicKey;

    private static volatile PrivateKey privateKey;

    private RSACryptoUtil() {
    }

    private static final Object lock = new Object();

    // 双重检查锁实现
    public static PublicKey getPublicKey() throws Exception {
        if (publicKey == null) {
            synchronized (lock) {
                if (publicKey == null) {
                    publicKey = publicKey();
                }
            }
        }
        return publicKey;
    }

    public static PrivateKey getPrivateKey() throws Exception {
        if (privateKey == null) {
            synchronized (lock) {
                if (privateKey == null) {
                    privateKey = privateKey();
                }
            }
        }
        return privateKey;
    }

    public static PublicKey publicKey() throws Exception {
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        ClassPathResource resource = new ClassPathResource(keystorePath);
        try (InputStream is = resource.getInputStream()) {
            keyStore.load(is, keystorePassword.toCharArray());
        }
        Certificate cert = keyStore.getCertificate(certAlias);
        return cert.getPublicKey();
    }

    public static PrivateKey privateKey() throws Exception {
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        ClassPathResource resource = new ClassPathResource(keystorePath);
        try (InputStream is = resource.getInputStream()) {
            keyStore.load(is, keystorePassword.toCharArray());
        }
        return (PrivateKey) keyStore.getKey(certAlias, keystorePassword.toCharArray());
    }

    /**
     * 加密
     *
     * @param data 待加密数据
     */
    public static String encrypt(String data) throws Exception {
        StopWatch stopWatch = new StopWatch("rsa-encrypt");
        stopWatch.start();
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.ENCRYPT_MODE, getPublicKey());
        String encoded = Base64.getEncoder()
                .encodeToString(cipher.doFinal(data.getBytes(StandardCharsets.UTF_8)));
        stopWatch.stop();
        log.info("rsa-encrypt: {}ms", stopWatch.getTotalTimeMillis());
        return encoded;
    }

    /**
     * 解密
     *
     * @param encryptedData 待解密数据
     */
    public static String decrypt(String encryptedData) throws Exception {
        StopWatch stopWatch = new StopWatch("rsa-decrypt");
        stopWatch.start();
        byte[] decode = Base64.getDecoder()
                .decode(encryptedData);
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.DECRYPT_MODE, getPrivateKey());
        byte[] decryptedBytes = cipher.doFinal(decode);
        String decrypted = new String(decryptedBytes, StandardCharsets.UTF_8);
        stopWatch.stop();
        log.info("rsa-decrypt: {}ms", stopWatch.getTotalTimeMillis());
        return decrypted;
    }

    /**
     * 生成签名
     * @param data 待签名数据
     * @return Base64编码的签名结果
     */
    public static String sign(String data) throws Exception {
        StopWatch stopWatch = new StopWatch("rsa-sign");
        stopWatch.start();
        Signature signature = Signature.getInstance("SHA256WithRSA");
        signature.initSign(getPrivateKey());
        signature.update(data.getBytes(StandardCharsets.UTF_8));
        String signed = Base64.getEncoder()
                .encodeToString(signature.sign());
        stopWatch.stop();
        log.info("rsa-sign: {}ms", stopWatch.getTotalTimeMillis());
        return signed;
    }

    /**
     * 验证签名
     * @param data 原始数据
     * @param sign 待验证的签名(Base64编码)
     * @return 验证结果
     */
    public static boolean verify(String data, String sign) throws Exception {
        StopWatch stopWatch = new StopWatch("rsa-verify");
        stopWatch.start();
        Signature signature = Signature.getInstance("SHA256WithRSA");
        signature.initVerify(getPublicKey());
        signature.update(data.getBytes(StandardCharsets.UTF_8));
        boolean verified = signature.verify(Base64.getDecoder()
                .decode(sign));
        stopWatch.stop();
        log.info("rsa-verify: {}ms", stopWatch.getTotalTimeMillis());
        return verified;
    }

}
